using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
using System.IO;
using TelnetExpect;
using System.Threading;
using System.Diagnostics;

namespace TelnetConsole
{
    class Program
    {
        ManualResetEvent m_quitting = new ManualResetEvent(false);
        bool m_ansi = false;
        StatusReceiveTelnetHandler m_rcvStatus;

        static void Main(string[] args)
        {
            if (!Debugger.IsAttached) { // If not seen in debugger console dump to stderr.
                Trace.Listeners.Add(new ConsoleTraceListener(true));
            }
            //
            //
            string host;
            int? port = null;
            if (args.Length == 0) {
                //host = "localhost"
                Console.Error.WriteLine("Supply the server hostname on the command-line.");
                Console.Out.WriteLine("Supply the server hostname on the command-line.");
                return;
                // cyberspace.org | arbornet.org | cvs.synchro.net
            } else {
                if (args[0].Equals("-expect",
                         StringComparison.CurrentCultureIgnoreCase)) {
                    string user, password;
                    GetArgs(args, 1, out host, out user, out password);
                    Expect1 xp = new Expect1(host, user, password);
                    xp.Run1_CheckServiceRunning("Windows Time");
                    return;
                }
                host = args[0];
                if (args.Length >= 2) {
                    port = int.Parse(args[1]);
                }
            }
            new Program().Run(host, port);
        }

        void Run(string host, int? port)
        {
            if (m_quitting.WaitOne(0)) {
                throw new InvalidOperationException("Can only be used once.");
            }
            //
            int port2 = TelnetStream.DefaultPort;
            if (port != null) {
                port2 = (int)port;
            }
            TcpClient cli;
            try {
                cli = new TcpClient(host, port2);
            } catch (SocketException sex) {
                Console.WriteLine(sex.Message);
                Environment.Exit(1);
                return;
            }
            Stream peer_ = cli.GetStream();
            //
            m_ansi = false;
            Console.Write("ANSI terminal? [y/N]> ");
            string line = Console.ReadLine();
            if (line != null) {
                if (line.Trim().Equals("y", StringComparison.CurrentCultureIgnoreCase)) {
                    m_ansi = true;
                }
            }
            //
            TelnetStream telnet;
            telnet = new TelnetStream(peer_);
            if (m_ansi) {
                telnet.AddOptionHandler(new ConsoleTerminalAnsiTelnetHandler());
            }
            m_rcvStatus = new StatusReceiveTelnetHandler();
            telnet.AddOptionHandler(m_rcvStatus);
            AuthenticationNtlmTelnetHandler tnap = new ConsoleAuthenticationNtlmTelnetHandler();
            tnap.SetTargetHostName(host);
            telnet.AddOptionHandler(tnap);
            telnet.AddOptionHandler(new EncryptionNullTelnetHandler());
            //
            Console.TreatControlCAsInput = true;
            //
            if (m_ansi) {
                ThreadPool.QueueUserWorkItem(Printer_Ansi, telnet);
            } else {
                ThreadPool.QueueUserWorkItem(Printer, telnet);
            }
            //Input_WithReadLine(telnet);
            ThreadPool.QueueUserWorkItem(Input_WithReadKey, telnet);
            //
            m_quitting.WaitOne();
            peer_.Close();
        }

        void Input_WithReadKey(object state)
        {
            try {
                TelnetStream telnet = (TelnetStream)state;
                TelnetExpect.ConsoleAnsiConsole console = new ConsoleAnsiConsole(telnet);
                StreamWriter wtr = new StreamWriter(telnet);
                wtr.NewLine = "\r\n"; // Ensure this.
                bool quit = false;
                while (true) {
                    //const bool Intercept = true;
                    while (true) {
                        if (Console.KeyAvailable) { break; }
                        quit = m_quitting.WaitOne(100);
                        if (quit) { break; }
                    }
                    if (quit) { break; }
                    ConsoleKeyInfo cki = Console.ReadKey(telnet.IsRemoteEcho);
                    if (cki.Key == ConsoleKey.RightWindows) { // Send STATUS request.
                        if (m_rcvStatus.IsSupported) {
                            m_rcvStatus.RequestStatus();
                        } else {
                            Trace.WriteLine("Remote does not support Status.");
                        }
                    }
#if SendTelnetDoDangerousDangerous
                        // HACK SendTelnetDoDangerousDangerous
                    else if (cki.Key == ConsoleKey.LeftWindows) {
                        for (int i = 0; i < 256; ++i) {
                            TelnetOption oc = (TelnetOption)i;
                            if (oc == TelnetOption.Logout) { //Skip for now!!!!
                                continue;
                            }
                            telnet.SendTelnetDoDangerousDangerous(oc);
                        }
                        telnet.SendTelnetDoDangerousDangerous(TelnetOption.Logout);
                    }
#endif
                    console.WriteKeyPressTo(cki, IsAnsiTerminalMode(telnet), wtr);
                    wtr.Flush();
                }//while
            } finally {
                m_quitting.Set();
            }
        }

        static bool IsAnsiTerminalMode(TelnetStream telnet)
        {
            bool enabled = telnet.GetLocalState(TelnetOption.TerminalType);
            // We should ideally check we send "ANSI" mode (last) -- but we only
            // send the single term-type "ANSI" so the server BETTER support it!
            return enabled;
        }

        void Input_WithReadLine(TelnetStream telnet)
        {
            StreamWriter wtr = new StreamWriter(telnet);
            wtr.NewLine = "\r\n"; // Ensure this.
            while (true) {
                string line = Console.ReadLine();
                if (line == null) {
                    break;
                }
                wtr.WriteLine(line);
                wtr.Flush();
            }//while
        }

        void Printer(object state)
        {
            try {
                TelnetStream telnet = (TelnetStream)state;
                byte[] buf = new byte[1024];
                while (true) {
                    int readLen = telnet.Read(buf, 0, buf.Length);
                    if (readLen == 0) {
                        Console.WriteLine("[Connection closed by peer.]");
                        break;
                    }
                    string text = Encoding.ASCII.GetString(buf, 0, readLen);
                    Console.Write(text);
                }//while
            } finally {
                m_quitting.Set();
            }
        }

        void Printer_Ansi(object state)
        {
            ConsoleColor fg = Console.ForegroundColor;
            ConsoleColor bg = Console.BackgroundColor;
            try {
                TelnetStream telnet = (TelnetStream)state;
                ConsoleAnsiConsole console = new ConsoleAnsiConsole(telnet);
                console.RunPrinterForever();
            } finally {
                m_quitting.Set();
                // Restore the original console colours!
                Console.ForegroundColor = fg;
                Console.BackgroundColor = bg;
            }
        }

        #region Misc
        private static void GetArgs(string[] args, int argOffset, out string hostname, out string user, out string password)
        {
            if (args.Length >= 1 + argOffset) {
                hostname = args[0 + argOffset];
            } else {
                hostname = ReadString("hostname");
            }
            //
            if (args.Length >= 2 + argOffset) {
                user = args[1 + argOffset];
            } else {
                user = ReadString("username");
            }
            //
            if (args.Length >= 3 + argOffset) {
                password = args[2 + argOffset];
            } else {
                password = ReadString("password");
            }
        }

        private static string ReadString(string prompt)
        {
            Console.Write(prompt + "> ");
            string t;
            while (true) {
                t = Console.ReadLine();
                if (t == null)
                    throw new EndOfStreamException();
                if (!string.IsNullOrEmpty(t)) {
                    break;
                }
            }
            return t;
        }
        #endregion

    }
}
